﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using SharpMUD.Framework.Workflow.Generic;

namespace SharpMUD.Framework.Net
{
    public class SocketReturningConnectionListener
        : INetworkConnectionListener, IPulseDependent
    {
        private System.Net.IPEndPoint _LocalEndPoint;
        private System.Net.Sockets.TcpListener _Listener;
        private Func<System.Net.Sockets.Socket, INetworkConnection> _ConnectionWrapperCreator;

        private bool _IsActive = false;

        private List<INetworkConnection> _PendingConnections;

        public SocketReturningConnectionListener(Func<System.Net.Sockets.Socket, INetworkConnection> connectionWrapperCreator, IPAddress localAddress, int port)
            : this(connectionWrapperCreator,new System.Net.IPEndPoint(localAddress, port))
        { }
        public SocketReturningConnectionListener(Func<System.Net.Sockets.Socket, INetworkConnection> connectionWrapperCreator, int port)
            : this(connectionWrapperCreator,new System.Net.IPEndPoint(IPAddress.Any,port))
        { }
        public SocketReturningConnectionListener(Func<System.Net.Sockets.Socket, INetworkConnection> connectionWrapperCreator, IPEndPoint localEndPoint)
        {
            if (localEndPoint == null)
                throw new ArgumentNullException("localEndPoint");
            if (connectionWrapperCreator == null)
                throw new ArgumentNullException("connectionWrapperCreator");

            _Listener = new System.Net.Sockets.TcpListener(localEndPoint);
            _PendingConnections = new List<INetworkConnection>();

            _LocalEndPoint = localEndPoint;
            _ConnectionWrapperCreator = connectionWrapperCreator;
        }

        #region INetworkConnectionListener Members

        public System.Net.IPEndPoint ListenPoint
        {
            get { return _LocalEndPoint; }
        }

        public IEnumerable<INetworkConnection> PendingConnections
        {
            get { return _PendingConnections; }
        }

        public void RemoveWaitingConnection(INetworkConnection value, bool accepted)
        {
            if (!this._PendingConnections.Contains(value))
                throw new InvalidOperationException();

            _PendingConnections.Remove(value);
            if (accepted)
            {
                if (this.ConnectionAccepted != null)
                    this.ConnectionAccepted(this, value);
            }
            else
            {
                if (this.ConnectionRejected != null)
                    this.ConnectionRejected(this, value);
            }   
        }

        public event ConnectionListenerEventHandler ConnectionAttempted;

        public event ConnectionListenerEventHandler ConnectionAccepted;

        public event ConnectionListenerEventHandler ConnectionRejected;

        public void Start()
        {
            lock (this._Listener)
            {
                if (!this._IsActive)
                {
                    this._Listener.Start();

                    this._IsActive = true;
                }
            }
        }

        public void Stop()
        {
            lock (this._Listener)
            {
                if (this._IsActive)
                {
                    this._Listener.Stop();

                    this._IsActive = false;
                }
            }
        }

        #endregion

        #region IPulseDependent Members

        public void Pulse()
        {
            List<System.Net.Sockets.Socket> pendingSockets = new List<System.Net.Sockets.Socket>();
            lock (this._Listener)
            {
                while (this._Listener.Pending())
                {
                    pendingSockets.Add(this._Listener.AcceptSocket());
                }
            }
            // NOTE: We lock this._PendingConnections here so we aren't locking it inside of a loop
            lock (this._PendingConnections)
            {
                foreach (var s in pendingSockets)
                { HandleConnectionAttempt(s); }
            }
        }

        #endregion

        private void HandleConnectionAttempt(System.Net.Sockets.Socket socket)
        {
            //NOTE: Don't lock this._PendingConnections because it is locked by the caller already
            // and this function is called from within a loop, so we don't want to lock repeatedly

            INetworkConnection networkConnection = this._ConnectionWrapperCreator(socket);
            _PendingConnections.Add(networkConnection);
            if (this.ConnectionAttempted != null)
                this.ConnectionAttempted(this, networkConnection);
        }
    }
}
